package com.such.kit.database;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.Stack;
import java.util.stream.Collectors;
import com.such.kit.database.dialect.ReservedWordFactory;
import com.such.kit.string.StringCommons;
import com.such.kit.validate.ValidateCommons;

public class SqlFormatter {

	private static List<RetractConfig> retractConfigs = new ArrayList<RetractConfig>();
	static {
		retractConfigs.add(new RetractConfig("select", RetractConfig.MODE_END, RetractConfig.MODE_END, "from"));
		retractConfigs.add(new RetractConfig("set", RetractConfig.MODE_END, RetractConfig.MODE_END, "where"));
		retractConfigs.add(new RetractConfig(",", RetractConfig.MODE_END, RetractConfig.MODE_END));
		retractConfigs.add(new RetractConfig("case", RetractConfig.MODE_BEGIN_AND_END, RetractConfig.MODE_BEGIN_AND_END, "end"));
		retractConfigs.add(new RetractConfig("when", RetractConfig.MODE_BEGIN, RetractConfig.MODE_BEGIN));
		retractConfigs.add(new RetractConfig("then", RetractConfig.MODE_BEGIN, RetractConfig.MODE_BEGIN));
		retractConfigs.add(new RetractConfig("else", RetractConfig.MODE_BEGIN, RetractConfig.MODE_BEGIN));
		retractConfigs.add(new RetractConfig("end", RetractConfig.MODE_BEGIN, RetractConfig.MODE_BEGIN));
		retractConfigs.add(new RetractConfig("(", RetractConfig.MODE_END, RetractConfig.MODE_END, ")"));
		retractConfigs.add(new RetractConfig(")", RetractConfig.MODE_BEGIN, RetractConfig.MODE_BEGIN));
		retractConfigs.add(new RetractConfig("from", RetractConfig.MODE_BEGIN, RetractConfig.MODE_BEGIN));
		retractConfigs.add(new RetractConfig("join", RetractConfig.MODE_BEGIN, RetractConfig.MODE_BEGIN));
		retractConfigs.add(new RetractConfig("left join", RetractConfig.MODE_BEGIN, RetractConfig.MODE_BEGIN));
		retractConfigs.add(new RetractConfig("right join", RetractConfig.MODE_BEGIN, RetractConfig.MODE_BEGIN));
		retractConfigs.add(new RetractConfig("inner join", RetractConfig.MODE_BEGIN, RetractConfig.MODE_BEGIN));
		retractConfigs.add(new RetractConfig("outer join", RetractConfig.MODE_BEGIN, RetractConfig.MODE_BEGIN));
		retractConfigs.add(new RetractConfig("where", RetractConfig.MODE_BEGIN_AND_END, RetractConfig.MODE_BEGIN_AND_END));
		retractConfigs.add(new RetractConfig("or", RetractConfig.MODE_BEGIN, RetractConfig.MODE_BEGIN));
		retractConfigs.add(new RetractConfig("and", RetractConfig.MODE_BEGIN, RetractConfig.MODE_BEGIN));
		retractConfigs.add(new RetractConfig("group", RetractConfig.MODE_BEGIN, RetractConfig.MODE_BEGIN));
		retractConfigs.add(new RetractConfig("having", RetractConfig.MODE_BEGIN_AND_END, RetractConfig.MODE_BEGIN_AND_END));
		retractConfigs.add(new RetractConfig("order", RetractConfig.MODE_BEGIN, RetractConfig.MODE_BEGIN));
		retractConfigs.add(new RetractConfig("by", RetractConfig.MODE_END, RetractConfig.MODE_END));
		retractConfigs.add(new RetractConfig("union", RetractConfig.MODE_BEGIN_AND_END, RetractConfig.MODE_BEGIN_AND_END));

		/*
		QUANTIFIERS.add("all");
		*/
	}

	public static String format(String sql) {
		StringBuffer sqlBuf = new StringBuffer();
		if (ValidateCommons.isNotEmpty(sql)) {
			sql = sql.trim();
			int sqlLen = sql.length();

			boolean hasWhiteSpace = false;
			boolean hasQuote = false;
			int clausesDepth = 0;
			StringBuffer clausesBuf = new StringBuffer();
			Stack<RetractConfig> configStack = new Stack<SqlFormatter.RetractConfig>();
			for (int i = 0; i < sqlLen; i++) {
				char ch = sql.charAt(i);
				// 跳过多余的空格
				if (' ' == ch) {
					if (!hasWhiteSpace) {
						clausesBuf.append(ch);
						hasWhiteSpace = true;
					}
					continue;
				} else {
					hasWhiteSpace = false;
				}
				// 检查是否有引号，如果有引号对特殊字符不做处理
				if ('\"' == ch || '`' == ch || '\'' == ch) {
					clausesBuf.append(ch);
					if (hasQuote) {
						hasQuote = false;
					} else {
						hasQuote = true;
					}
					continue;
				}
				// 遇 SQL 结束符 ; 则进行换行
				if (!hasQuote && ';' == ch) {
					clausesBuf.append(ch).append(StringCommons.NEW_LINE);
					sqlBuf.append(clausesBuf);
					clausesBuf = new StringBuffer();
					clausesDepth = 0;
					continue;
				}
				// 开始进行子句处理
				clausesBuf.append(ch);
				if (!hasQuote) {
					String clauses = clausesBuf.toString();
					// 处理需要换行的特殊字符
					MatcherResult matcherResult = new MatcherResult(false);
					for (RetractConfig retractConfig : retractConfigs) {
						String key = retractConfig.getKey();
						String initialLineMode = retractConfig.getInitialLineMode();
						String retractMode = retractConfig.getRetractMode();
						Set<String> closeKeys = retractConfig.getCloseKeys();
						if (clauses.startsWith(key) || clauses.endsWith(key)) {
							// 检查是否为完整匹配的 key
							// 完整匹配的 key 后应跟特殊字符
							if (i + 1 < sqlLen && !ReservedWordFactory.isReservedSymbol(key)) {
								ch = sql.charAt(i + 1);
								if (!ReservedWordFactory.isReservedSymbol(String.valueOf(ch)) && ' ' != ch) {
									continue;
								}
							}
							if (ValidateCommons.isNotEmpty(closeKeys)) {
								// 当 closeKeys 不为空，则为 open key，clausesDepth + 1
								configStack.push(retractConfig);
								clausesDepth++;
							} else if (ValidateCommons.isNotEmpty(configStack)) {
								// 当 closeKeys 为空，且与最后一个 open key 的 closeKeys 匹配，clausesDepth - 1
								RetractConfig checkConfig = configStack.peek();
								Set<String> checkKeys = checkConfig.getCloseKeys();
								if (checkKeys.contains(key)) {
									// open key close 后从 configStack 中踢除
									configStack.pop();
									clausesDepth--;
								}
							}
							StringBuffer replacement = new StringBuffer();
							if (initialLineMode.indexOf(RetractConfig.MODE_BEGIN) > -1) {
								replacement.append(StringCommons.NEW_LINE);
								// 检查前置字符是否是空格，是则删除
								int bufLen = sqlBuf.length();
								if (bufLen > 0) {
									bufLen = bufLen - 1;
									ch = sqlBuf.charAt(bufLen);
									hasWhiteSpace = (' ' == ch);
									while (hasWhiteSpace) {
										sqlBuf.deleteCharAt(bufLen);
										ch = sql.charAt(bufLen - 1);
										hasWhiteSpace = (' ' == ch);
									}
								}
							}
							if (retractMode.indexOf(RetractConfig.MODE_BEGIN) > -1) {
								replacement.append(StringCommons.repeat(StringCommons.RETRACT, clausesDepth));
							}
							replacement.append(key);
							if (initialLineMode.indexOf(RetractConfig.MODE_END) > -1) {
								replacement.append(StringCommons.NEW_LINE);
								if (i + 1 < sqlLen) {
									// 检查后续字符是否是空格，是则删除
									ch = sql.charAt(i + 1);
									hasWhiteSpace = (' ' == ch);
									while (hasWhiteSpace) {
										i++;
										ch = sql.charAt(i + 1);
										hasWhiteSpace = (' ' == ch);
									}
								}
							}
							if (retractMode.indexOf(RetractConfig.MODE_END) > -1) {
								replacement.append(StringCommons.repeat(StringCommons.RETRACT, clausesDepth));
							}
							clauses = clauses.replace(key, replacement.toString());
							matcherResult = new MatcherResult(true, clauses, clausesDepth);
						}
					}
					if (matcherResult.isMatched()) {
						sqlBuf.append(matcherResult.getClauses());
						clausesBuf = new StringBuffer();
						clausesDepth = matcherResult.getClausesDepth();
					}
				}
			}
			// 循环结束后检查是否还有未处理的子句
			if (clausesBuf.length() > 0) {
				sqlBuf.append(clausesBuf);
			}
		}
		return sqlBuf.toString();
	}

	private static class RetractConfig {

		public static final String MODE_BEGIN = "B";
		public static final String MODE_END = "E";
		public static final String MODE_BEGIN_AND_END = "BAE";

		private String key;
		private String initialLineMode;
		private String retractMode;
		private Set<String> closeKeys;

		public RetractConfig(String key, String initialLineMode, String retractMode, String... closeKeys) {
			this.key = key;
			this.initialLineMode = initialLineMode;
			this.retractMode = retractMode;
			if (ValidateCommons.isNotEmpty(closeKeys)) {
				this.closeKeys = new HashSet<String>(Arrays.stream(closeKeys).collect(Collectors.toList()));
			}
		}

		public String getKey() {
			return this.key;
		}

		public String getInitialLineMode() {
			return this.initialLineMode;
		}

		public String getRetractMode() {
			return this.retractMode;
		}

		public Set<String> getCloseKeys() {
			return this.closeKeys;
		}

	}

	private static class MatcherResult {

		private boolean matched;
		private String clauses;
		private int clausesDepth;

		public MatcherResult(boolean matched) {
			this.matched = matched;
		}

		public MatcherResult(boolean matched, String clauses, int clausesDepth) {
			this.matched = matched;
			this.clauses = clauses;
			this.clausesDepth = clausesDepth;
		}

		public boolean isMatched() {
			return this.matched;
		}

		public String getClauses() {
			return this.clauses;
		}

		public int getClausesDepth() {
			return this.clausesDepth;
		}

	}

}
